<?php

namespace APP\Ctrl;
use CMS\Ctrl as Base;
use CMS\Helper as Helper;

class Search extends Base {
	private $permissions = array();

	// construct
	public function run() {
		// set permissions
		$this->permissions();

		// manage uri paths
		if(count($this->uri) > 0) {
			@list($func, $id, $page) = $this->uri;
			$this->$func($id ? $id : 'null', $page ? $page : 0);
		} else $this->index();
	}

	// permissions
	private function permissions() {
		// source from auth or guest
		if(isset($this->session->auth)) $source = $this->session->auth;
		else if(isset($this->session->guest)) $source = $this->session->guest;
		else $source = array();

		// set view permissions
		if(isset($source['permissions']['forum']['view']))
			$this->permissions = $source['permissions']['forum']['view'];
	}

	// index
	public function index() {
		$this->view->func = 'index';

		// query based on permissions
		$permissions = $this->db->escapeString('WHERE t.category IN (' . implode(',', $this->permissions) . ')');

		// find 10 most used words
		$result = $this->db->query("SELECT
			COUNT(*) AS match, w.word

			FROM search AS s
			LEFT JOIN words AS w ON s.word = w.id
			LEFT JOIN replies AS r ON s.reply = r.id
			LEFT JOIN topics AS t ON r.topic = t.id

			{$permissions}

			GROUP BY s.word
			ORDER BY match DESC LIMIT 20
		");

		// transform
		$data = array();
		while($res = $result->fetchArray(SQLITE3_ASSOC)) {
			$data[] = array(
				'word' => $res['word'],
				'hits' => $res['match'],
				'link' => base64_encode(serialize(array($res['word'])))
			);
		}

		// output data
		$this->view->data = $data;

		// set path
		$this->view->path = array(
			'func' => $this->view->func
		);
	}

	// break up text into searchable words
	private function validate($text, $escape = false) {
		// Remove BBCode
		$text = preg_replace('/\[\/?(b|u|i|quote|img|url)(?:\=[^\]]*)?\]/', ' ', $text);

		// Remove any apostrophes which aren't part of words
		$text = substr(preg_replace('((?<=\W)\'|\'(?=\W))', '', ' '.$text.' '), 1, -1);

		// Remove symbols and multiple whitespace
		$text = preg_replace('/[\^\$&\(\)<>`"\|,@_\?%~\+\[\]{}:=\/#\\\\;!\*\.\s]+/', ' ', $text);

		// go through each word
		$list = array();
		$words = array_unique(explode(' ', strtolower($text)));
		foreach($words as $word) {
			// word smaller than n letters or bigger than n? abort
			if(strlen($word) < $this->settings['search_min'] || strlen($word) > $this->settings['search_max'])
				continue;

			// found in stop list? abort
			if(in_array($word, explode(', ', STOP_LIST)))
				continue;

			// push into array
			$list[] = $escape ? $this->db->escapeString($word) : $word;
		}

		return $list;
	}

	// search results
	public function results($id, $page) {
		$this->view->func = 'results';
		$this->view->id = $id;

		// prepare vars
		$page = $this->db->escapeString((int)$page);
		$list = unserialize(base64_decode($id));

		// send to view
		$this->view->query = implode(', ', $list);
		$this->view->list = json_encode($list);

		// validate again in case of malicious url
		$list = $this->validate(implode(' ', $list), true);

		// compile query
		$query = "word LIKE '%" . implode("%' OR word LIKE '%", $list) . "%'";

		// query based on permissions
		$permissions = $this->db->escapeString('WHERE t.category IN (' . implode(',', $this->permissions) . ')');

		// count replies
		$count = $this->db->querySingle("SELECT
			COUNT(match) as nr

			FROM (SELECT COUNT(s.reply) AS match
				FROM search AS s
				INNER JOIN (SELECT id
					FROM words WHERE {$query}
				) AS w ON s.word = w.id
				LEFT JOIN replies AS r ON r.id = s.reply
				LEFT JOIN topics AS t ON t.id = r.topic

				{$permissions}

				GROUP BY s.reply
			)
		");
		$lastpage = ceil($count / $this->settings['nav_replies']);
		if($page < 1) $page = 1;
		if($page > $lastpage) $page = $lastpage;
		$this->view->pagination = Helper::paginate($lastpage, $page);
		$limit = $count ? ('LIMIT ' .($page - 1) * $this->settings['nav_replies'] .',' . $this->settings['nav_replies']) : null;

		// find the replies
		$result = $this->db->query("SELECT
			COUNT(s.reply) AS match,
			r.id, r.message, r.time,
			a.id AS author_id, a.email As author_email, a.name As author_name, x.name AS author_rank,
			t.id AS topic_id, t.name AS topic_name

			FROM search AS s
			INNER JOIN (SELECT id
				FROM words WHERE {$query}
			) AS w ON s.word = w.id
			LEFT JOIN replies AS r ON r.id = s.reply
			LEFT JOIN users AS a ON r.author = a.id
			LEFT JOIN ranks AS x ON a.rank = x.id
			LEFT JOIN topics AS t ON t.id = r.topic

			{$permissions}

			GROUP BY s.reply
			ORDER BY match DESC, r.time DESC {$limit}
		");

		// transform
		$data = array();
		while($res = $result->fetchArray(SQLITE3_ASSOC)) {
			$data[] = array(
				'id' => $res['r.id'],
				'author_id' => $res['author_id'],
				'author_avatar' => md5(strtolower($res['author_email'])),
				'author_name' => $res['author_name'],
				'author_link' => $res['author_id'] . '-' . Helper::generate_named_id($res['author_name']),
				'author_rank' => $res['author_rank'],
				'time' => Helper::format_time($res['r.time']),
				'topic_id' => $res['topic_id'],
				'topic_name' => $res['topic_name'],
				'message' => Helper::format_message($res['r.message'])
			);
		}

		// output data
		$this->view->data = $data;

		// set title
		$this->view->title .= ' / ' . $this->view->query;

		// set path
		$this->view->path = array(
			'func' => $this->view->func,
			'id' => (string)$id,
			'page' => (int)$page
		);
	}

	// on submit, filter out the search query
	public function submit() {
		// get from post
		$query = $this->request->post('query');
		if(is_null($query))
			return $this->json = array('success' => false);

		// get some valid words out of the query
		$list = $this->validate($query);

		// list is empty?
		if(empty($list))
			return $this->json = array('success' => false, 'msg' => 'Query is invalid');

		// encode query
		return $this->json = array('success' => true, 'string' => base64_encode(serialize($list)));
	}
}